﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;
using EnvDTE;
using EnvDTE80;

namespace KindOfMagic
{
  [Flags]
  public enum CommandStates
  {
    None = 0,
    Visible = 0x01,
    Enabled = 0x02
  }

  public enum CommandAction
  {
    Enable,
    Disable
  }

  public sealed class Wizard
  {
    DTE2 _app;

    public Wizard(DTE2 app)
    {
      _app = app;
    }

    public void UpdateCommand(MenuCommand cmd, CommandAction action)
    {
      var states = GetStatus(action);
      cmd.Visible = (CommandStates.Visible & states) != 0;
      cmd.Enabled = (CommandStates.Enabled & states) != 0;
    }

    public CommandStates GetStatus(CommandAction action)
    {
      return GetCommandStatus(GetMultiStatus(), action);
    }

    public void ApplyKindOfMagic(CommandAction action)
    {
      var projects = SupportedProjects;
      if (projects == null)
        return;

      if (action == CommandAction.Enable)
        projects.RemoveAll(IsKindOfMagicEnabled);
      else
        projects.RemoveAll(IsKindOfMagicDisabled);

      foreach (var proj in projects)
        if (proj.IsDirty)
          proj.Save();

      if (action == CommandAction.Enable)
        projects.ForEach(EnableKindOfMagic);
      else
        projects.ForEach(DisableKindOfMagic);

      foreach (var proj in projects)
        _projects.Remove(proj.FullName);
    }

    void DisableKindOfMagic(Project file)
    {
      var e = XElement.Load(file.FullName);

      RemoveImports(e);

      e.Save(file.FullName);
    }

    void RemoveImports(XElement e)
    {
      var imports = FindImport(e, false).ToList();

      foreach (var import in imports)
        import.Remove();
    }

    const string _targets = "KindOfMagic.targets";
    static readonly string _path = Path.Combine(GetInstallDir(), _targets);

    static string GetInstallDir()
    {
      var x = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
      var home = Environment.GetEnvironmentVariable("KINDOFMAGIC");

      if (x != home)
      {
        Environment.SetEnvironmentVariable("KINDOFMAGIC", x, EnvironmentVariableTarget.User);
        Environment.SetEnvironmentVariable("KINDOFMAGIC", x);
      }

      return "$(KINDOFMAGIC)";
    }

    void EnableKindOfMagic(Project file)
    {
      var e = XElement.Load(file.FullName);

      RemoveImports(e);

      e.Add(new XElement(_import, new XAttribute("Project", _path)));
      e.Save(file.FullName);
    }

    static bool IsSupported(Project proj)
    {
      return proj.UniqueName.EndsWith(".csproj", StringComparison.InvariantCultureIgnoreCase);
    }

    public List<Project> SupportedProjects
    {
      get
      {
        var items = _app.ActiveSolutionProjects as IEnumerable;
        if (items == null)
          return null;

        var result = items.OfType<Project>().ToList();
        if (!result.Any() || !result.All(IsSupported))
          return null;

        return result;
      }
    }

    CommandStates GetCommandStatus(int status, CommandAction action)
    {
      if (status == 0)
        return CommandStates.None;

      var result = ((action == CommandAction.Disable ? status >> 1 : status) & 1) != 0;

      if (result)
        return CommandStates.Enabled | CommandStates.Visible;

      return CommandStates.None;
    }

    int GetMultiStatus()
    {
      var result = 0;

      var projects = SupportedProjects;
      if (projects == null)
        return result;

      if (projects.Any(IsKindOfMagicDisabled))
        result |= 1;

      if (projects.Any(IsKindOfMagicEnabled))
        result |= 2;

      return result;
    }

    class ProjectInfo
    {
      bool _isEnabled;
      WeakReference _project;
      public ProjectInfo(Project project, bool isEnabled)
      {
        _project = new WeakReference(project);
        _isEnabled = isEnabled;
      }

      public Project Project { get { return _project.Target as Project; } }
      public bool IsEnabled { get { return _isEnabled; } }
    }

    Dictionary<string, ProjectInfo> _projects = new Dictionary<string, ProjectInfo>(StringComparer.InvariantCultureIgnoreCase);

    /// <summary>
    /// Retrives cached KindOfMagic status for a project 
    /// </summary>
    bool IsKindOfMagicEnabled(Project project)
    {
      string projectFullName;
      try
      {
        projectFullName = project.FullName;
      }
      catch (NotImplementedException)
      {
        return false;
      }

      ProjectInfo result;
      if (_projects.TryGetValue(project.FullName, out result) && result.Project == project)
        return result.IsEnabled;

      _projects[project.FullName] = result = new ProjectInfo(project, GetStatus(project.FullName));

      return result.IsEnabled;
    }

    bool IsKindOfMagicDisabled(Project project)
    {
      return !IsKindOfMagicEnabled(project);
    }

    static readonly XName _import = XName.Get("Import", "http://schemas.microsoft.com/developer/msbuild/2003");

    /// <summary>
    /// Retrieves all nodes representing KindOfMagic
    /// </summary>
    static IEnumerable<XElement> FindImport(XElement root, bool strict)
    {
      var candidates = from e in root.Elements(_import)
                       let a = e.Attribute("Project")
                       where a != null
                       select new { Element = e, Project = (string)a };

      if (strict)
        return from i in candidates
               where string.Equals(i.Project, _path, StringComparison.InvariantCultureIgnoreCase)
               select i.Element;

      return from i in candidates
             let file = Path.GetFileName(i.Project)
             where string.Equals(file, _targets, StringComparison.InvariantCultureIgnoreCase)
             select i.Element;
    }

    /// <summary>
    /// Retrieves whether KindOfMagic enabled or not
    /// </summary>
    /// <param name="projectFile">Path to project file</param>
    bool GetStatus(string projectFile)
    {
      try
      {
        return FindImport(XElement.Load(projectFile), true).Any();
      }
      catch (FileNotFoundException)
      {
        return false;
      }
    }
  }
}
